iT邦幫忙

2025 iThome 鐵人賽

DAY 21
0

前言

歡迎來到第 21 天!昨天我們完成了後端的整理,讓路由本身變得更為單純,把服務的邏輯抽出管理,在結構上看起來遠比之前好看多了。我們昨天做的努力,除了本來就該做這樣的整理之外,另一方面則是為了後續的功能新增鋪路。過去 20 天,我們的應用程式是「無狀態」的,每個使用者進來,體驗都一樣。從今天起,我們要賦予它「記憶」。我們要建立一個真正的使用者系統,並打造一個專門的資料庫來儲存每一次的練習紀錄,讓我們的使用者之後可以透過練習紀錄來回顧自己的學習歷程,同時也讓我們的儀表板之後能有真正的資料套入。

今日目標

  • 在 Supabase 中啟用 Authentication 功能,為使用者註冊與登入打下基礎。
  • 設計並建立一張新的 practice_records 資料表,用來儲存每一次的面試練習紀錄。
  • 學習並啟用 RLS (Row Level Security),確保使用者只能看到自己的資料,保障資料安全。
  • 理解今天的後端基礎建設,與未來幾天前後端串接的關係。

Step 1: 啟用 Supabase Authentication

要記住使用者,第一步就是要能識別他們。Supabase 提供了非常強大且易於整合的 Auth 服務,請按照以下的步驟操作:

  1. 前往 Supabase 儀表板:登入你的 Supabase 專案,如果你照教學走的話就是找ai-interviewer-knowledge-base專案。
  2. 找到 Authentication:在左側的選單中,點擊那個「使用者」圖示的「Authentication」選項。
圖1
圖1 :Authentication頁面
  1. 啟用 Email Provider:在 Users 頁面中,找到左側的 Sign In / Providers 按鈕,點擊後照圖片中的設置啟用Email Provider,這個步驟會允許使用者透過 Email 做登入與註冊,並自動發送認證信。
圖2
圖2 :啟用Email Provider

完成這一步,你的 Supabase 後端就具備了處理使用者註冊、登入、登出的能力了。這就像我們蓋好了一家銀行的「開戶系統」,系統本身已經準備就緒,現有的平台服務在做 auth 的部分遠比以前簡化了許多,對於我們這樣的示範專案來說可說是再適合不過了。

補充說明:

你可能會想:「我們今天又不寫登入頁面,為什麼要先啟用 Authentication?」
這是一個很好的問題,答案在於技術相依性。我們即將建立的 practice_records 資料表,其中有一個 user_id 欄位,它需要參考 (REFERENCES) Supabase 內建的 auth.users 表。而 auth.users 這張儲存所有使用者資訊的特殊資料表,只有在你第一次啟用 Authentication 功能後,Supabase 才會自動幫你建立。
所以,我們的執行順序必須是:
啟用 Auth -> 讓 auth.users 表被建立出來。
建立 practice_records 表 -> 才能成功地將 user_id 關聯到 auth.users

Step 2: 設計 practice_records 資料表

這是我們今天最核心的任務。這張表將是我們所有使用者數據的基石,就像是銀行的「金庫」。一個好的資料庫設計,能讓未來的開發事半功倍。

我們需要的欄位如下:

欄位名稱 (name) 型別 (type) 說明
id uuid 主鍵 (Primary Key)。使用 uuid_generate_v4() 自動生成。
created_at timestamptz 紀錄的建立時間。使用 now() 作為預設值。
user_id uuid 【核心關聯】外鍵,關聯到 auth.users 表的 id。用來標示這筆紀錄是誰的。
question_id text 練習的題目 ID,對應到我們 questions.json 中的 id。
user_answer text 使用者提交的完整答案(文字或程式碼)。
evaluation jsonb AI 回傳的完整評估 JSON 物件。使用 jsonb 型別可以讓我們未來對 JSON 內容進行高效查詢。
score int2 從 evaluation 中提取出的分數 (1-5)。雖然 evaluation 裡有,但獨立出來方便未來做統計與排序。

如果你對於後端資料表比較沒什麼接觸的話,那你可能會好奇「使用者和練習紀錄是如何對應的?」,我這邊簡單說明一下,這個問題的核心就在於 user_id 這個欄位。整個流程如下:

程式碼片段

graph TD
    A[使用者A 註冊/登入] --> B{Supabase Auth}
    B --> C[在 auth.users 表中<br/>建立一筆使用者紀錄<br/>得到獨一無二的 User_A_ID]
    
    D[使用者A 提交答案] --> E{我們的 /api/interview/evaluate API}
    E --> F[1.取得當前登入者的 ID<br/>也就是 User_A_ID]
    F --> G[2.將練習結果連同<br/>User_A_ID 一起寫入]
    G --> H[在 practice_records 表中<br/>建立一筆新紀錄]
    
    C -.-> I[practice_records.user_id]
    H -.-> I
    
    style I fill:#f9f,stroke:#333,stroke-width:2px

簡單來說,每個使用者註冊時都會獲得一個獨一無二的 user_id。當我們儲存練習紀錄時,會把這個 user_id 一起存進去,就像在每筆紀錄上蓋上主人的印章,這樣就能清楚地將使用者和他們的練習紀錄完美地對應起來。

Step 3: 透過 SQL 建立資料表

設計好了,我們就用 Supabase 的 SQL Editor 來建立它。

  1. 前往 SQL Editor:在左側選單點擊「SQL Editor」。
  2. 新增查詢:點擊「New query」。
  3. 貼上並執行:將下方的 SQL 指令貼上,然後點擊「RUN」。
-- 建立 practice_records 資料表
CREATE TABLE public.practice_records (
  id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
  created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
  user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE NOT NULL,
  question_id TEXT NOT NULL,
  user_answer TEXT,
  evaluation JSONB,
  score SMALLINT CHECK (score >= 1 AND score <= 5)
);

-- 建立索引以提升查詢效能
CREATE INDEX idx_practice_records_user_id ON public.practice_records(user_id);
CREATE INDEX idx_practice_records_created_at ON public.practice_records(created_at DESC);
CREATE INDEX idx_practice_records_question_id ON public.practice_records(question_id);

-- 啟用 RLS 
ALTER TABLE public.practice_records ENABLE ROW LEVEL SECURITY;

指令摘要說明

這段 SQL 腳本主要執行了三大任務:建立結構、優化效能、以及設定安全基礎。

  1. 建立 practice_records 主資料表:
    此指令定義了儲存每筆練習紀錄的核心結構,包含 id、user_id、evaluation (用於儲存 AI 的 JSON 回應) 等關鍵欄位。
    它建立了兩個重要的「約束 (Constraints)」:
  • REFERENCES auth.users(id) ON DELETE CASCADE:確保每筆紀錄都必須對應到一個真實的使用者,並且當使用者帳號被刪除時,其所有相關的練習紀錄也會自動一併清除。
  • CHECK (score >= 1 AND score <= 5):在資料庫層級強制要求 score 欄位的值只能在 1 到 5 之間,從源頭保證了資料的正確性。
  1. 建立三組「索引 (Indexes)」以加速查詢:
    這部分是為了未來的效能考量。我們在 user_idcreated_at (降序)、和 question_id 這三個未來最可能被用來篩選或排序的欄位上建立了索引。
    這能確保當資料量變大時,使用者在查詢自己的練習歷史、按最新時間排序、或篩選特定題目的紀錄時,都能獲得極速的回應。
  2. 加上「註解 (Comment)」並啟用「資料級安全 (RLS)」:
  • 每段的上方加上註解為資料表本身提供了一段說明,增加了程式碼的可讀性和可維護性,方便未來的自己或其他協作者快速理解其用途。
  • ENABLE ROW LEVEL SECURITY 則是整個使用者資料安全的核心。它像一個總開關,預設會攔截所有讀取請求,為我們下一步撰寫「使用者只能讀取自己資料」的安全規則鋪平了道路。

補充說明:什麼是 RLS ?

Row Level Security (RLS),中文常譯為「資料列級安全」或「行級安全」,是 PostgreSQL 資料庫一個極其強大的安全功能,也是 Supabase 安全模型的基石。
傳統的資料庫權限管理通常是針對「整張表」的,例如「Danny 這個使用者可以讀取 practice_records 這張表」。但這會帶來一個問題:Danny 可以讀取表中所有人的練習紀錄,這顯然是我們不想要的。
RLS 則將權限管理細化到了「每一列資料」,確保即便前端出了什麼差錯,其他使用者也無法讀取資料庫中的他人資料。

執行成功後,你就可以在「Table Editor」中看到這張全新的、空無一物的 practice_records 表了!

圖3
圖3 :新增的空資料表

今日回顧

今天我們為專案的「產品化」邁出了巨大的一步,從一個無狀態的工具,變成了一個具備使用者系統基礎的平台。

✅ 我們成功在 Supabase 中啟用了 Authentication 功能。
✅ 我們設計並建立了一張結構良好的 practice_records 資料表。
✅ 我們為新資料表開啟了 RLS,從第一天就貫徹了安全優先的原則。

重要:今天我們完成的是所有後端基礎建設。使用者還不能真的註冊,練習紀錄也還不會被儲存。這需要我們在接下來兩天完成前後端的串接工作。

明日預告

資料庫的地基已經打好,門鎖也已裝上。明天 (Day 22),我們就要來打造真正的「大門」和「玄關」了。我們將回到前端,建立登入和註冊頁面,並整合 Supabase 的客戶端 Auth SDK,讓使用者能夠真正地登入我們的應用程式,並為讀取他們專屬的儀表板數據做好準備!


上一篇
小小技術債處理:別讓以後的自己痛苦 Part 2
下一篇
建立 Auth:整合 Supabase SSR
系列文
前端工程師的AI應用開發實戰:30天從Prompt到Production - 以打造AI前端面試官為例23
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言